package main

import (
	"fmt"
	"sync"
)

func doWork(id int, c chan int, done chan bool) {
	for n := range c {
		fmt.Printf("Worker %d received %c\n", id, n)
		go func() { done <- true }() // 如果不开协程处理，由于是大小写发了两次，会造成死锁
	}
}

type worker struct {
	in   chan int
	done chan bool
}

func createWorker(id int) worker {
	w := worker{
		in:   make(chan int),
		done: make(chan bool),
	}
	go doWork(id, w.in, w.done)
	return w
}

func chanDemo() {
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		workers[i].in <- 'a' + i
		<-workers[i].done // 每个channel发送完数据就等待，会造成顺序打印
	}

	for i := 0; i < 10; i++ {
		workers[i].in <- 'A' + i
		<-workers[i].done
	}
}

func chanDemo2() {
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWorker(i)
	}

	for i, worker := range workers {
		worker.in <- 'a' + i
	}

	for i, worker := range workers {
		worker.in <- 'A' + i
	}

	// wait for all of them
	for _, worker := range workers {
		<-worker.done
		<-worker.done
	}
}

// #3
type worker3 struct {
	in chan int
	wg *sync.WaitGroup // 只能使用指针，因为是要用外面的wg，不能使用拷贝的wg
}

func doWork3(id int, c chan int, wg *sync.WaitGroup) {
	for n := range c {
		fmt.Printf("Worker %d received %c\n", id, n)
		wg.Done()
	}
}

func createWorker3(id int, wg *sync.WaitGroup) worker3 {
	w := worker3{
		in: make(chan int),
		wg: wg,
	}
	go doWork3(id, w.in, w.wg)
	return w
}

func chanDemo3() {
	var wg sync.WaitGroup

	var workers [10]worker3
	for i := 0; i < 10; i++ {
		workers[i] = createWorker3(i, &wg)
	}

	wg.Add(20)
	for i, worker := range workers {
		worker.in <- 'a' + i
	}

	for i, worker := range workers {
		worker.in <- 'A' + i
	}

	wg.Wait()
}

// #4 使用函数式编程优化wg作为参数
type worker4 struct {
	in   chan int
	done func()
}

func doWork4(id int, w worker4) { // 抽象掉了wg
	for n := range w.in {
		fmt.Printf("Worker %d received %c\n", id, n)
		w.done()
	}
}

func createWorker4(id int, wg *sync.WaitGroup) worker4 {
	w := worker4{
		in: make(chan int),
		done: func() {
			wg.Done()
		},
	}
	go doWork4(id, w)
	return w
}

func chanDemo4() {
	var wg sync.WaitGroup

	var workers [10]worker4
	for i := 0; i < 10; i++ {
		workers[i] = createWorker4(i, &wg)
	}

	wg.Add(20)
	for i, worker := range workers {
		worker.in <- 'a' + i
	}

	for i, worker := range workers {
		worker.in <- 'A' + i
	}

	wg.Wait()
}

func main() {
	//chanDemo()
	//chanDemo2()
	//chanDemo3()
	chanDemo4()
}
